#pragma once

#include <thrust/reduce.h>

template <typename Container>
struct Reduce
{
  Container A;

  template <typename Range>
  Reduce(const Range& X)
    : A(X.begin(), X.end())
  {}

  void operator()(void)
  {
    thrust::reduce(A.begin(), A.end());
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container,
          typename T>
struct ReduceInit
{
  Container A;
  T init;

  template <typename Range>
  ReduceInit(const Range& X, T init)
    : A(X.begin(), X.end()),
      init(init)
  {}

  void operator()(void)
  {
    thrust::reduce(A.begin(), A.end(), init);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container,
          typename T,
          typename BinaryOperation>
struct ReduceInitBinaryOp
{
  Container A;
  T init;
  BinaryOperation binary_op;

  template <typename Range>
  ReduceInitBinaryOp(const Range& X, T init, BinaryOperation binary_op)
    : A(X.begin(), X.end()),
      init(init),
      binary_op(binary_op)
  {}

  void operator()(void)
  {
    thrust::reduce(A.begin(), A.end(), init, binary_op);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3,
          typename Container4>
struct ReduceByKey
{
  Container1 A;
  Container2 B;
  Container3 C;
  Container4 D;

  template <typename Range1, typename Range2, typename Range3, typename Range4>
  ReduceByKey(const Range1& X, const Range2& Y, const Range3& Z, const Range4& W)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end()),
      D(W.begin(), W.end())
  {}

  void operator()(void)
  {
    thrust::reduce_by_key(A.begin(), A.end(), B.begin(), C.begin(), D.begin());
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3,
          typename Container4,
          typename BinaryPredicate>
struct ReduceByKeyPred
{
  Container1 A;
  Container2 B;
  Container3 C;
  Container4 D;
  BinaryPredicate pred;

  template <typename Range1, typename Range2, typename Range3, typename Range4>
  ReduceByKeyPred(const Range1& X, const Range2& Y, const Range3& Z, const Range4& W, BinaryPredicate pred)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end()),
      D(W.begin(), W.end()),
      pred(pred)
  {}

  void operator()(void)
  {
    thrust::reduce_by_key(A.begin(), A.end(), B.begin(), C.begin(), D.begin(), pred);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3,
          typename Container4,
          typename BinaryPredicate,
          typename BinaryOperation>
struct ReduceByKeyPredBinaryOp
{
  Container1 A;
  Container2 B;
  Container3 C;
  Container4 D;
  BinaryPredicate pred;
  BinaryOperation binary_op;

  template <typename Range1, typename Range2, typename Range3, typename Range4>
  ReduceByKeyPredBinaryOp(const Range1& X, const Range2& Y, const Range3& Z, const Range4& W, BinaryPredicate pred, BinaryOperation binary_op)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end()),
      D(W.begin(), W.end()),
      pred(pred),
      binary_op(binary_op)
  {}

  void operator()(void)
  {
    thrust::reduce_by_key(A.begin(), A.end(), B.begin(), C.begin(), D.begin(), pred, binary_op);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

